Crash Reporting mechanism (in BL31)
-----------------------------------
-NOTE: This section assumes that your platform is enabling the MULTI_CONSOLE_API
-flag in its platform.mk. Not using this flag is deprecated for new platforms.
-
BL31 implements a crash reporting mechanism which prints the various registers
of the CPU to enable quick crash analysis and debugging. This mechanism relies
on the platform implementating ``plat_crash_console_init``,
output to be routed over the normal console infrastructure and get printed on
consoles configured to output in crash state. ``console_set_scope()`` can be
used to control whether a console is used for crash output.
+NOTE: Platforms are responsible for making sure that they only mark consoles for
+use in the crash scope that are able to support this, i.e. that are written in
+assembly and conform with the register clobber rules for putc() (x0-x2, x16-x17)
+and flush() (x0-x3, x16-x17) crash callbacks.
In some cases (such as debugging very early crashes that happen before the
normal boot console can be set up), platforms may want to control crash output
-more explicitly. For these, the following functions can be overridden by
-platform code. They are executed outside of a C environment and without a stack.
-
-If this behaviour is not desirable, the platform may implement functions that
-redirect the prints to the console driver (``console_xxx_core_init``, etc). Most
-platforms (including Arm platforms) do this and they can be used as an example.
+more explicitly. These platforms may instead provide custom implementations for
+these. They are executed outside of a C environment and without a stack. Many
+console drivers provide functions named ``console_xxx_core_init/putc/flush``
+that are designed to be used by these functions. See Arm platforms (like juno)
+for an example of this.
Function : plat\_crash\_console\_init [mandatory]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
console. It must only use the general purpose registers x0 through x7 to do the
initialization and returns 1 on success.
-When using the sample implementation, if you are trying to debug crashes before
-the console driver would normally get registered, you can use this to register a
-driver from assembly with hardcoded parameters. For example, you could register
-the 16550 driver like this:
-
-::
-
- .section .data.crash_console /* Reserve space for console structure */
- crash_console:
- .zero 6 * 8 /* console_16550_t has 6 8-byte words */
- func plat_crash_console_init
- ldr x0, =YOUR_16550_BASE_ADDR
- ldr x1, =YOUR_16550_SRCCLK_IN_HZ
- ldr x2, =YOUR_16550_TARGET_BAUD_RATE
- adrp x3, crash_console
- add x3, x3, :lo12:crash_console
- b console_16550_register /* tail call, returns 1 on success */
- endfunc plat_crash_console_init
-
-If you're trying to debug crashes in BL1, you can call the
-``console_xxx_core_init`` function exported by some console drivers from here.
-
Function : plat\_crash\_console\_putc [mandatory]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
x2 to do its work. The parameter and the return value are in general purpose
register x0.
-If you have registered a normal console driver in ``plat_crash_console_init``,
-you can keep the sample implementation here (which calls ``console_putc()``).
-
-If you're trying to debug crashes in BL1, you can call the
-``console_xxx_core_putc`` function exported by some console drivers from here.
-
Function : plat\_crash\_console\_flush [mandatory]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
registers x0 through x5 to do its work. The return value is 0 on successful
completion; otherwise the return value is -1.
-If you have registered a normal console driver in ``plat_crash_console_init``,
-you can keep the sample implementation here (which calls ``console_flush()``).
-
-If you're trying to debug crashes in BL1, you can call the console_xx_core_flush
-function exported by some console drivers from here.
-
External Abort handling and RAS Support
---------------------------------------
#error "This crash console implementation only works with the MULTI_CONSOLE_API!"
#endif
- /* -----------------------------------------------------
- * int plat_crash_console_init(void)
- * Use normal console by default. Switch it to crash
- * mode so serial consoles become active again.
- * NOTE: This default implementation will only work for
- * crashes that occur after a normal console (marked
- * valid for the crash state) has been registered with
- * the console framework. To debug crashes that occur
- * earlier, the platform has to override these functions
- * with an implementation that initializes a console
- * driver with hardcoded parameters. See
- * docs/porting-guide.rst for more information.
- * -----------------------------------------------------
+ /*
+ * Spinlock to syncronize access to crash_console_triggered. We cannot
+ * acquire spinlocks when the cache is disabled, so in some cases (like
+ * late during CPU suspend) some risk remains.
*/
-func plat_crash_console_init
-#if defined(IMAGE_BL1)
+.section .data.crash_console_spinlock
+ define_asm_spinlock crash_console_spinlock
+
/*
- * BL1 code can possibly crash so early that the data segment is not yet
- * accessible. Don't risk undefined behavior by trying to run the normal
- * console framework. Platforms that want to debug BL1 will need to
- * override this with custom functions that can run from registers only.
+ * Flag to make sure that only one CPU can write a crash dump even if
+ * multiple crash at the same time. Interleaving crash dumps on the same
+ * console would just make the output unreadable, so it's better to only
+ * get a single but uncorrupted dump. This also means that we don't have
+ * to duplicate the reg_stash below for each CPU.
*/
- mov x0, #0
- ret
-#else /* IMAGE_BL1 */
- mov x3, x30
- mov x0, #CONSOLE_FLAG_CRASH
- bl console_switch_state
+.section .data.crash_console_triggered
+ crash_console_triggered: .byte 0
+
+ /*
+ * Space to stash away some register values while we're calling into
+ * console drivers and don't have a real stack available. We need x14,
+ * x15 and x30 for bookkeeping within the plat_crash_console functions
+ * themselves, and some console drivers use x16 and x17 as additional
+ * scratch space that is not preserved by the main crash reporting
+ * framework. (Note that x16 and x17 should really never be expected to
+ * retain their values across any function call, even between carefully
+ * designed assembly functions, since the linker is always free to
+ * insert a function call veneer that uses these registers as scratch
+ * space at any time. The current crash reporting framework doesn't
+ * really respect that, but since TF is usually linked as a single
+ * contiguous binary of less than 128MB, it seems to work in practice.)
+ */
+.section .data.crash_console_reg_stash
+ .align 3
+ crash_console_reg_stash: .quad 0, 0, 0, 0, 0
+
+ /* --------------------------------------------------------------------
+ * int plat_crash_console_init(void)
+ * Takes the crash console spinlock (if possible) and checks the trigger
+ * flag to make sure we're the first CPU to dump. If not, return an
+ * error (so crash dumping will fail but the CPU will still call
+ * plat_panic_handler() which may do important platform-specific tasks
+ * that may be needed on all crashing CPUs). In either case, the lock
+ * will be released so other CPUs can make forward progress on this.
+ * Clobbers: x0 - x4, x30
+ * --------------------------------------------------------------------
+ */
+func plat_crash_console_init
+#if defined(IMAGE_BL31)
+ mov x4, x30 /* x3 and x4 are not clobbered by spin_lock() */
+ mov x3, #0 /* return value */
+
+ mrs x1, sctlr_el3
+ tst x1, #SCTLR_C_BIT
+ beq skip_spinlock /* can't synchronize when cache disabled */
+
+ adrp x0, crash_console_spinlock
+ add x0, x0, :lo12:crash_console_spinlock
+ bl spin_lock
+
+skip_spinlock:
+ adrp x1, crash_console_triggered
+ add x1, x1, :lo12:crash_console_triggered
+ ldarb w2, [x1]
+ cmp w2, #0
+ bne init_error
+
+ mov x3, #1 /* set return value to success */
+ stlrb w3, [x1]
+
+init_error:
+ bl spin_unlock /* harmless if we didn't acquire the lock */
+ mov x0, x3
+ ret x4
+#else /* Only one CPU in BL1/BL2, no need to synchronize anything */
mov x0, #1
- ret x3
+ ret
#endif
endfunc plat_crash_console_init
- /* -----------------------------------------------------
- * void plat_crash_console_putc(int character)
- * Output through the normal console by default.
- * -----------------------------------------------------
+ /* --------------------------------------------------------------------
+ * int plat_crash_console_putc(char c)
+ * Prints the character on all consoles registered with the console
+ * framework that have CONSOLE_FLAG_CRASH set. Note that this is only
+ * helpful for crashes that occur after the platform intialization code
+ * has registered a console. Platforms using this implementation need to
+ * ensure that all console drivers they use that have the CRASH flag set
+ * support this (i.e. are written in assembly and comply to the register
+ * clobber requirements of plat_crash_console_putc().
+ * --------------------------------------------------------------------
*/
func plat_crash_console_putc
- b console_putc
+ adrp x1, crash_console_reg_stash
+ add x1, x1, :lo12:crash_console_reg_stash
+ stp x14, x15, [x1]
+ stp x16, x17, [x1, #16]
+ str x30, [x1, #32]
+
+ mov w14, w0 /* W14 = character to print */
+ adrp x15, console_list
+ ldr x15, [x15, :lo12:console_list] /* X15 = first console struct */
+
+putc_loop:
+ cbz x15, putc_done
+ ldr w1, [x15, #CONSOLE_T_FLAGS]
+ tst w1, #CONSOLE_FLAG_CRASH
+ b.eq putc_continue
+ ldr x2, [x15, #CONSOLE_T_PUTC]
+ cbz x2, putc_continue
+ mov x1, x15
+ blr x2
+ mov w0, w14
+putc_continue:
+ ldr x15, [x15] /* X15 = next struct */
+ b putc_loop
+
+putc_done:
+ adrp x1, crash_console_reg_stash
+ add x1, x1, :lo12:crash_console_reg_stash
+ ldp x14, x15, [x1]
+ ldp x16, x17, [x1, #16]
+ ldr x30, [x1, #32]
+ ret
endfunc plat_crash_console_putc
- /* -----------------------------------------------------
- * void plat_crash_console_flush(void)
- * Flush normal console by default.
- * -----------------------------------------------------
+ /* --------------------------------------------------------------------
+ * int plat_crash_console_flush(char c)
+ * Flushes all consoles registered with the console framework that have
+ * CONSOLE_FLAG_CRASH set. Same requirements as putc().
+ * --------------------------------------------------------------------
*/
func plat_crash_console_flush
- b console_flush
+ adrp x1, crash_console_reg_stash
+ add x1, x1, :lo12:crash_console_reg_stash
+ stp x30, x15, [x1]
+ stp x16, x17, [x1, #16]
+
+ adrp x15, console_list
+ ldr x15, [x15, :lo12:console_list] /* X15 = first console struct */
+
+flush_loop:
+ cbz x15, flush_done
+ ldr w1, [x15, #CONSOLE_T_FLAGS]
+ tst w1, #CONSOLE_FLAG_CRASH
+ b.eq flush_continue
+ ldr x2, [x15, #CONSOLE_T_FLUSH]
+ cbz x2, flush_continue
+ mov x0, x15
+ blr x2
+flush_continue:
+ ldr x15, [x15] /* X15 = next struct */
+ b flush_loop
+
+flush_done:
+ adrp x1, crash_console_reg_stash
+ add x1, x1, :lo12:crash_console_reg_stash
+ ldp x30, x15, [x1]
+ ldp x16, x17, [x1, #16]
+ ret
endfunc plat_crash_console_flush